package vcd_loader
import scala.io.Source
import scala.io.BufferedSource
import scala.collection.mutable
import scala.collection.mutable.ArrayBuffer
import javax.swing.text.Segment
import java.util.NoSuchElementException
import scala.util.matching.Regex
import scala.util.matching.Regex.Match

class VChange(val data:BigInt,val time: Long){
    def toInt:Int = data.toInt
    def toLong:Long = data.toLong
}

class VSignal(val typename:String,val width:Int,val nodeid:String,val name:String,val msb:Int,val lsb:Int){
    val edges = new ArrayBuffer[VChange]()
    var last:BigInt = null

    def appendValue(s:String,t:Long){
        val n = s.length()
        last = BigInt(s,2)
        if(VCDLoader.save)
            edges.append(new VChange(last,t))
    }


    def iter = VSignalIter(this)
}

case class VSignalIter(val sig:VSignal){
    var i = 0
    def t = sig.edges(i).time
    def setTime(tick:Int){
        if(tick < t){
            if(i == 0){
                println(s"Time $tick is before the 1st value dumped")
                return
            }
            i -= 1
            if(tick >= t)
                return
            locate(tick,0,i)
        }
        else {
            if(i == sig.edges.length - 1 || tick < sig.edges(i+1).time)
                return
            i += 1
            if(i == sig.edges.length - 1 || tick < sig.edges(i+1).time)
                return
            locate(tick,i+1,sig.edges.length)
        }
    }

    @annotation.tailrec
    private def locate(tick:Long,l:Int,r:Int):Int = {
        if(l + 1 >= r) l
        else {
            val m = (l+r)>>1
            if(tick < sig.edges(m).time) locate(tick,l,m)
            else locate(tick,m,r)
        }
    }

    def checkRange(){
        assert(i >= 0 && i < sig.edges.length)
    }
    def toInt = sig.edges(i).toInt
    def toLong = sig.edges(i).toLong
    def getData = sig.edges(i).data
}

class VScope(val typename:String,val name:String,val parent:VScope){
    var complete  = false
    val l_signals = new ArrayBuffer[VSignal]()
    val l_scopes  = new ArrayBuffer[VScope ]()

    def addScope(s:VScope){
        l_scopes.append(s)
    }
    def addSignal(s:VSignal){
        l_signals.append(s)
    }
    if (parent != null){
        parent.addScope(this)
    }
    def getFlattenSignals():ArrayBuffer[VSignal] = {
        assert(complete)
        l_scopes.map(_.getFlattenSignals).fold(l_signals)(_ ++ _)
    }
    def getMapSignals():Map[String,VSignal]={
        assert(complete)
        return l_signals.map(x=>(x.name,x)).toMap
    }
    def getMapScopes():Map[String,VScope]={
        assert(complete)
        return l_scopes.map(x=>(x.name,x)).toMap
    }
    lazy val signals:Map[String,VSignal] = getMapSignals()
    lazy val scopes :Map[String,VScope ] = getMapScopes()

    def getSignal(hier:String):VSignal = {
        val names = hier.split("\\.")
        var cur = getScope(names.slice(0,names.length-1))
        cur.signals(names.last)
    }

    def getScope(names:Array[String]):VScope = {
        var cur = this
        for(sname <- names){
            cur = cur.scopes(sname)
        }
        cur
    }

    def getScope(hier:String):VScope = {
        getScope(hier.split("\\."))
    }
}

object VCDLoader{
    val debug = false
    var save  = false
    val p_space = """[ \t\n]"""
    val p_space_any = p_space + "*"
    val p_space_has = p_space + "+"
    def mkPattern(parts:String*):String = {
        return p_space_any + parts.mkString(p_space_has)+p_space_any
    }
    val r_info = mkPattern(
        """([^\$]*?)[ \t\n]*\$end"""
    ).r
    val r_cmd   = mkPattern("""\$(scope|var|upscope|version|date|timescale|enddefinitions)""").r
    val r_scp   = mkPattern(
        """\$scope""",
        """(module|begin)""",
        """([^ \t\n]+)""",
        """\$end"""
    ).r
    val p_var_bool = mkPattern(
        """(wire|reg)""",
        """([0-9]+)""",
        """([^ \t\n]+)""",
        """([^ \t\n]+)"""
    )
    val p_end = mkPattern( """\$end""" )
    val p_var = """\$var[ \t]+""" + p_var_bool + """(\[([0-9]+):([0-9]+)\][ \t\n]+)?""" + p_end
    val r_var = p_var.r
    def getMatchOf(r:Regex,line:String):Match = {
        val ret = r.findPrefixMatchOf(line)
        if(ret.isEmpty){
            printf("Regex \"%s\" cannot match \"%s\"\n",r.toString,line)
        }
        ret.get
    }
    def getInfo(tail:String,lines:Iterator[String]):String = {
        var line = tail
        val data = ArrayBuffer[String]()
        var done = false
        while(!done){
            val res = r_info.findPrefixMatchOf(line)
            val msg = (
                if(res.nonEmpty){
                    done = true
                    res.get.group(1)
                }
                else {
                    line
                }
            ).trim
            if(msg != "")
                data.append(msg)
            if(!done)
                line = lines.next
        }
        return data.mkString(" ")
    }
    def parseHead(lines:Iterator[String]):(mutable.Map[String,String],VScope,Long) = {
        val header = mutable.Map[String,String]()
        var top:VScope = null
        var done = false
        while(lines.hasNext && !done){
            val line = lines.next
            if(debug)
                println(s"Parsing $line")
            val m_cmd  = getMatchOf(r_cmd,line)
            val tail = line.substring(m_cmd.end)
            m_cmd.group(1) match {
                case "version"   => header.put("version"  ,getInfo(tail,lines))
                case "date"      => header.put("date"     ,getInfo(tail,lines))
                case "timescale" => header.put("timescale",getInfo(tail,lines))
                case "scope"     => {
                    val (typename,name) = parseScopeLine(line)
                    top = new VScope(typename,name,null)
                    parseScope(top,lines)
                }
                case "enddefinitions"=> done = true
            }
        }
        val ts = {
            val line = lines.next
            if(line(0)!='#'){
                println("\"\"\"")
                println(line)
                println("\"\"\"")
                assert(false)
            }
            line.substring(1).toInt.toLong
        }

        // Consumes $dumpvars
        {
            val line = lines.next
            assert(line == "$dumpvars")
        }
        (header,top,ts)
    }
    def parseScopeLine(line:String):(String,String) = {
        val m_scp = getMatchOf(r_scp,line)
        return (m_scp.group(1), m_scp.group(2))
    }

    def parseScope(top:VScope,lines:Iterator[String]){
        var scope   = top
        val signals = mutable.Map[String,VSignal]()
        while(lines.hasNext && scope != null){
            val line = lines.next
            if(debug)
                println(s"Parsing $line")
            val m_cmd = getMatchOf(r_cmd,line)
            m_cmd.group(1) match{
                case "scope"   => 
                    val (typename, name) = parseScopeLine(line)
                    scope = new VScope(typename,name,scope)
                case "var"     => {
                    val m_var  = getMatchOf(r_var,line)
                    val nodeid = m_var.group(3)
                    scope.addSignal(
                        if(signals.contains(nodeid)) signals(nodeid)
                        else {
                            val typename = m_var.group(1)
                            val width    = m_var.group(2).toInt
                            //  nodeid
                            val name     = m_var.group(4)
                            val is_bool  = m_var.group(5) == null
                            val msb      = if(is_bool) 0 else m_var.group(6).toInt
                            val lsb      = if(is_bool) 0 else m_var.group(7).toInt
                            val sig      = new VSignal(typename,width,nodeid,name,msb,lsb)
                            signals.put(nodeid,sig)
                            sig
                        }
                    )
                }
                case "upscope" => 
                    scope.complete = true
                    scope = scope.parent
            }
        }
    }

    val r_time = """([0-9]+)[ \t\n]*""".r
    val r_bin  = """([01xz]+)[ \t\n]+([^ \t\n]+)[ \t\n]*""".r
    val r_bool = """([01xz])([^ \t\n]+)[ \t\n]*""".r
    def addValueTo(signals:Map[String,VSignal],r:Regex,line:String,t:Long){
        val m_bin = getMatchOf(r,line)
        val nodeid = m_bin.group(2)
        val binstr = m_bin.group(1)
        val sig = signals(nodeid)
        sig.appendValue(binstr,t)
    }
    def parseBodyContent(signals:Map[String,VSignal],lines:Iterator[String],ts:Long,clk:VSignal=null, act:Long=>Unit = (i:Long)=>Unit, reset:VSignal=null):Long = {
        var t = ts
        var i = 0
        var reset_state = if(reset == null) 2 else 0
        var clk_level = false
        while(lines.hasNext){
            val line = lines.next
            i += 1
            if((i & 0xfffff) == 0)
                println(s"Parsed $i lines @$t")
            line(0) match {
                case '#' => {
                    val t_next = getMatchOf(r_time,line.substring(1)).group(1).toLong
                    if(clk != null && clk.last != null){
                        if(reset_state < 2){
                            reset_state match{
                                case 0 =>{
                                    if(reset.last.testBit(0)){
                                        reset_state = 1
                                    }
                                }
                                case 1 =>{
                                    if(!reset.last.testBit(0)){
                                        reset_state = 2
                                    }
                                }
                            }
                        }
                        // sample on negedge
                        if(reset_state == 2 && clk_level && !clk.last.testBit(0)){
                            act(t)
                        }
                        clk_level = clk.last.testBit(0)
                    }
                    t = t_next
                }
                case 'b' => 
                    addValueTo(signals,r_bin ,line.substring(1),t)
                case '$' =>
                    assert(line.trim == "$end")
                case _ =>
                    addValueTo(signals,r_bool,line,t)
            }
        }
        t
    }
    val r_dumpvars = mkPattern(
        
    ).r
}

class VCDLoader(val path:String){
    private val lines = Source.fromFile(path).getLines
    val (header,top,ts) = VCDLoader.parseHead(lines)
    val signals = top.getFlattenSignals().map(x => (x.nodeid,x)).toMap
    var te = 0L
    def load(){
        te = VCDLoader.parseBodyContent(signals, lines, ts)
    }
    def sim(act:Long=>Unit){
        te = VCDLoader.parseBodyContent(signals, lines, ts, top.signals("clk"), act, top.signals("reset"))
    }
}
